<?php
/**
 * Oracle
 *
 * @package php.db
 */

/**
 * 오라클 연결 클래스  
 *
 * @package php.db
 */
class Oracle extends DBClient { 

	private $tempStmt;
	private $cursor = array();

	/**
	 * 생성자
	 *
	 */
	public function __construct($option = '', $type = 'product') {
		parent::__construct($option, $type);
	}

	/**
	 * database 연결 생성
	 * 
	 * @return resource 연결 resource
	 */
	public function _connect() {
		// 10g 전용
		if ($this->version == '10') { 
			$sid = sprintf("//%s/%s", $this->host, $this->db);
		} else { 
			$sid = $this->db;
		}

		return oci_connect($this->id, $this->pass, $sid, $this->charset, constant($this->session_mode));
	}

	/**
	 * 연결 닫기 
	 */
	public function close() {

		if ($this->isConnected()) {
			oci_close($this->getConnection());
		}
	}

	/** 
	 * commit 
	 *
	 * @return bool
	 */
	public function commit() {
		return oci_commit($this->getConnection());
	}

	public function define($column, $var, $type) {
		return oci_define_by_name($this->getResult(), $column, $var, $type);
	}

	/**
	 * 에러 메세지 리턴
	 * 
	 * @return string 에러 메세지 
	 */
	public function error() {
		$arr = oci_error();

		return $arr['message'];
	}

	/**
	 * 실행된 select 쿼리에 대해서 fetch 를 통해 한 행(row)을 리턴한다. 
	 *
	 * 리턴되는 값은 array('필드' => 'ㅁㅁㅁ', ...); 형태를 가진다. 
	 * 
	 * @param resource $stmt select 결과로 나온 resource
	 * @return array row 데이타 
	 */
	public function fetch($stmt) {		
		return oci_fetch_array($stmt, OCI_ASSOC + OCI_RETURN_LOBS + OCI_RETURN_NULLS);
	}

	/**
	 * 전체 패치
	 *
	 * @param resource $stmt 파싱된 Statement 객체 
	 */
	public function fetchAll($stmt) { 
		oci_fetch_all($stmt, $temp, 0, -1, OCI_ASSOC +  OCI_FETCHSTATEMENT_BY_ROW);

		foreach ($temp as &$arr) { 
			$arr = array_change_key_case($arr, CASE_LOWER);
		}
		
		return $temp;
	}	

	/** 
	 * 메모리 해제 
	 * 
	 * @return bool
	 */
	public function free($result) { 
		return $this->freeStatement();
	}

	/**
	 * 쿼리의 결과로 나온 필드의 개수를 리턴한다.
	 *
	 * @param resource $stmt select 결과로 나온 resource
	 * @return int 필드개수 
	 */
	public function getFieldCount($stmt) {
		return oci_num_fields($stmt);
	}

	/**
	 * 정해진 위치의 필드 이름을 얻어온다. 
	 *
	 * @param resource $stmt select 결과로 나온 resource
	 * @param int $i 필드 위치, 처음은 0 
	 * @return string 필드이름 
	 */
	public function getFieldName($stmt, $i) {
		return oci_field_name($stmt, $i+1);
	}

	/**
	 * 정해진 위치의 필드 타입을 얻어온다. 
	 *
	 * @param resource $stmt select 결과로 나온 resource
	 * @param int $i 필드 위치, 처음은 0 
	 * @return string 필드타입
	 */
	public function getFieldType($stmt, $i) {
		return oci_field_type($stmt, $i+1);
	}

	/**
	 * DBData  구함
	 * 
	 * <code>
	 * $db = DB_('oracle', 'test', true);
	 * 
	 * $data = $db->getData("select 1 from dual");
	 * echo $data;
	 * 
	 * $db->close();
	 * </code>
	 *
	 * @param	string $query DB query
	 * @param	boolean $isOne 인덱스 한칸 옮기기
	 * @param	string $baseClass 생성될 클래스 이름 
	 * @return	DBData 
	 */
	public function getData($query, $isOne = false) 
	{

		$this->prepare($query);
		$this->execute();

		return $this->getBindData($isOne);
	} 

	/**
	 * cursor 에서 DBData 얻기
	 *
	 * <code>
	 * 
	 * $db = DB_('oracle', 'test', true);
	 * 
	 * $cursor = $db->newCursor('test_cursor');
	 * 
	 * ....... 
	 * 
	 * $data = $db->getCursorData('test_cursor');
	 * echo $data;
	 * 
	 * $db->close();
	 * 
	 * </code>
	 * 
	 * @return DBData 
	 */
	function getCursorData($name, $isOne = false) {

		$data = $this->createData($this->cursor[$name]);

		// 한 라인 짜리라면 true
		// true 이면 next를 바로 한 상태로 넘어온다. 
		if ($isOne) 
			$data->next();

		return $data;
	}

	/**
	 * select 쿼리에 대해서 페이징된 DBData를 리턴한다. 
	 *
	 * <code>
	 *
	 * $db = DB_('oracle', 'test', true);
	 *
	 * $data = $db->getPageData("select 1 from dual", 1, 10);
	 * echo $data;
	 *
	 * $db->close();
	 * 
	 * </code>
	 * 
	 * @param string $query select 쿼리
	 * @param int $page 현재 페이지  
	 * @param int $count 페이지당 리스트 개수 
	 * @param string $baseClass  DBData 를 상속한 클래스 , 기본값 DBData  
	 * @return DBData
	 */
	public function getPageData($query, $page, $count = 10) {

		// 시작지점 계산하기 
		$start = ($page-1)*$count+1;
		$end = $start + $count-1;

		$sql = " SELECT * FROM ( SELECT a.*, RowNum as rNum FROM ( $query ) a WHERE RowNum <= $end ) WHERE $start <= rNum";

		return $this->getData($sql, false);
	}


	/**
	 * 필드 타입(자료형)별 php용 전용 상수 얻어오기 
	 *
	 * @param string $type 타입문자열 
	 * @return int|string PHP상수
	 */
	public function getTypeConstant($type)
	{
		$type = strtoupper($type);

		switch ($type) {
			case 'FILE' :
				return SQLT_FILE;
			case 'CFILE' :
				return SQLT_CFILE;
			case 'CLOB': 
				return SQLT_CLOB;
			case 'BLOB':
				return SQLT_BLOB;
			case 'INT':
			case 'NUMBER':
				return SQLT_INT;
			case 'VARCHAR':
			case 'VARCHAR2':
				return SQLT_CHR;
			case 'LONG':
				return SQLT_LNG;
			case 'LBI':
				return SQLT_LBI;
			case 'BIN':					// binary
			case 'BINARY':				
				return SQLT_BIN;
			case 'RDD':					// rowid
			case 'ROWID':					
				return SQLT_RDD;
			case 'NTY':					// named types				
				return SQLT_NTY;
			case 'RSET':				// cursor
			case 'CURSOR':				
				return SQLT_RSET;
			default: 
				return -1;
		}
	}

	/**
	 * 필드 타입(자료형)별 DB에 입력될 수 있는 문자열 얻어오기 
	 *
	 * @param string $type 타입문자열 
	 * @param mixed $value 입력값
	 * @param boolean $is_null 널을 체크할 것인지 여부 , true 널체크, false 널 체크 안함 
	 * @return string 변환된 문자열
	 */
	public function getTypeString($type, $value, $is_null = false, $option = array('date_format' => 'YYYYMMDDHH24MISS')) { 
		$type = strtoupper($type);
		$temp = '';
		switch ($type) {
			case 'CHAR': 
			case 'NCHAR': 
			case 'NVARCHAR2' :
			case 'VARCHAR2':
				$temp = sprintf("'%s'", $this->escape($value));
				break;
			case 'DATE':
				$temp = sprintf("TO_DATE('%s', '%s')", $this->escape($value), $option['date_format']);
				break;
			case 'NUMBER':
				$temp = $value;
				break;
			case 'BLOB':
			case 'CLOB':
			case 'LONG RAW' :
			case 'LONG' : 
			case 'NCLOB':
			case 'ROW' :
				$temp = sprintf("'%s'", $this->escape($value));
				break;
			default: 
				$temp = $value;
				break;
		}

		return ($is_null && is_null($value)) ? 'NULL' : $temp;
	}


	/**
	 * execute 메소드를 실행했을 때 미리 읽어올 레코드셋 개수를 설정한다. 
	 *
	 * @param resource $stmt 실행된 statement
	 * @param int $count 미리 읽어올 레코드셋 개수 
	 * @return bool  성공했으면 true, 실패했으면 false 
	 */
	public function setPrefetch($stmt, $count = 10) { 
		return oci_set_prefetch($stmt, $count);
	}

	/**
	 * 커서 생성하기 
	 * 
	 * @param string $name 커서이름 
	 */
	public function newCursor($name) {
		$this->cursor[$name] = oci_new_cursor($this->getConnection());
	}

	/** 
	 * 일반 쿼리 실행 
	 * 
	 * @param string $sql 실행될 query 
	 * @param bool $isCommit commit 여부 , 바로 commit 이면 true, 아니면 false
	 * @param array $args 바인딩될 변수 목록 
	 */
	public function query($query, $isCommit = true, $args = array()) {
		$this->prepare($query);


		foreach ($args as $key => $value) { 
			$this->bind($key, $value);
		}

		$this->execute();

		if ($isCommit) {
			$this->commit();
		}

		return $this->getResult();
	}

	/** 
	 * rollback
	 *
	 * @return bool  성공이면 true, 실패이면 false 
	 */
	public function rollback() {
		return oci_rollback($this->getConnection());
	}

	/**
	 * 프로시저 실행
	 * 
	 * <code>
	 *  // 1. 일반적인 실행방법 
	 * 
	 *  // 2. 임의의 구문 실행방법 
	 * </code> 
	 * 
	 * @return bool 성공이면 true, 실패이면 false
	 * @todo insert, update ,delete 구문에 대해서 자동으로  binding 할 수 있는 기능 구현 
	 */
	public function spExecute() {
	
		$bind_list = "";
		$keys = array_keys($this->__sp_bind_list);

		// 전체 문법 적용시
		if ($this->__sp_option['type'] == 'sql') { 
			$str = $this->__sp_name;
		} 
		// 매개변수별 적용시 
		else { 

			// 바인딩 리스트 만들기 
			foreach ( $keys as $key) { 
				// 이름 설정 
				$bind_list[] = ':'.$this->__sp_bind_list[$key]['name'];
			}

			$bind_list = implode(",", $bind_list);

			// 프로시저 문자열 만들기 
			$str = "BEGIN {$this->__sp_name}($bind_list); END;";
		}


		$this->__sp_statement = $this->prepare($str);

		// 바인딩 시키기 
		foreach ($keys as $key) { 

			$this->bind(
				$this->__sp_bind_list[$key]['name'],
				$this->__sp_bind_list[$key]['type'],
				$this->__sp_bind_list[$key]['output'],
				$this->__sp_bind_list[$key]['length'],
				$this->__sp_bind_list[$key]['value']			
			);
		}

		$this->__sp_execute_result = $this->execute(OCI_DEFAULT);

		// 프로시저 실행 
		return $this->__sp_execute_result;
	
	}

	public function getResult() { 
		return $this->getStatement();
	}


	/**
	 * sql 구문 파싱하기 
	 *
	 * @param string $query 파싱될 쿼리
	 * @return resource 파싱된 statment resource
	 */
	public function prepare($query) {
		$this->addSql($query);

		$this->setStatement(oci_parse($this->getConnection(), $query));
		return $this->getStatement();
	}


	/** 
	 * bind 메소드 
	 *
	 * @param string $name 바인딩 이름 
	 * @param string $type 바인딩될 타입, 데이타베이스 마다 틀림 
	 * @param string $output output 형태, in, out, inout, return 
	 * @param int $length 데이타길이, -1은 길이제한 없음
	 * @param mixed $value 저장될 데이타 또는 output 변수에 저장될 데이타
	 * @return bool 
	 */
	public function bind($name, $type = '', $output = 'in', $length = -1, &$value = null) {
		$param = ":".$name;		// 이름 변경 

		return oci_bind_by_name($this->getResult(), $param, $value, $length, $this->getTypeConstant($type));
	}

	/**
	 * 쿼리 실행 
	 *
	 * <code>
	 * $db = DB_('oracle', 'test', true);
	 *
	 * $db->prepare("insert into test_table values ('1', '2')");
	 * $db->execute();		// 쿼리 실행 
	 * $db->commit();
	 *
	 * $db->close();
	 * </code>
	 * 
	 * @return resource statement를 실행한 리소스 
	 */
	public function execute($mode = OCI_DEFAULT) {
		$this->setExecute(oci_execute($this->getStatement(), $mode));
		return $this->getExecute();
	}

	/**
	 * prepare 메모리 해제 
	 * 
	 * @param resource $stmt oci_prepare() 함수로 나온 결과 resource 
	 * @return bool  
	 */
	public function freeStatement($stmt) {
		return oci_free_statement($stmt);
	}

	/**
	 * 프로시저를 실행할 객체를 얻는다. 
	 * 
	 * 
	 * @see lib/php/db/DBClient#createProc($name)
	 * @return OracleProc 
	 */
	public function createProc($str, $option = 'proc') { 
		return new OracleProc($this, $str, $option);
	}
}

?>